home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / spy.js < prev    next >
Text File  |  2010-01-15  |  30KB  |  970 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10.  
  11. // List of contexts with XHR spy attached.
  12. var contexts = [];
  13.  
  14. // ************************************************************************************************
  15. // Spy Module
  16.  
  17. /**
  18.  * @module Represents a XHR Spy module. The main purpose of the XHR Spy feature is to monitor
  19.  * XHR activity of the current page and create appropriate log into the Console panel.
  20.  * This feature can be controlled by an option <i>Show XMLHttpRequests</i> (from within the
  21.  * console panel).
  22.  * 
  23.  * The module is responsible for attaching/detaching a HTTP Observers when Firebug is
  24.  * activated/deactivated for a site.
  25.  */
  26. Firebug.Spy = extend(Firebug.Module,
  27. /** @lends Firebug.Spy */
  28. {
  29.     dispatchName: "spy",
  30.  
  31.     initialize: function()
  32.     {
  33.         if (Firebug.TraceModule)
  34.             Firebug.TraceModule.addListener(this.TraceListener);
  35.  
  36.         Firebug.Module.initialize.apply(this, arguments);
  37.     },
  38.  
  39.     shutdown: function()
  40.     {
  41.         Firebug.Module.shutdown.apply(this, arguments);
  42.  
  43.         if (Firebug.TraceModule)
  44.             Firebug.TraceModule.removeListener(this.TraceListener);
  45.     },
  46.  
  47.     initContext: function(context)
  48.     {
  49.         context.spies = [];
  50.  
  51.         if (Firebug.showXMLHttpRequests && Firebug.Console.isAlwaysEnabled())
  52.             this.attachObserver(context, context.window);
  53.  
  54.     },
  55.  
  56.     destroyContext: function(context)
  57.     {
  58.         // For any spies that are in progress, remove our listeners so that they don't leak
  59.         this.detachObserver(context, null);
  60.  
  61.         delete context.spies;
  62.  
  63.     },
  64.  
  65.     watchWindow: function(context, win)
  66.     {
  67.         if (Firebug.showXMLHttpRequests && Firebug.Console.isAlwaysEnabled())
  68.             this.attachObserver(context, win);
  69.     },
  70.  
  71.     unwatchWindow: function(context, win)
  72.     {
  73.         try
  74.         {
  75.             // This make sure that the existing context is properly removed from "contexts" array.
  76.             this.detachObserver(context, win);
  77.         }
  78.         catch (ex)
  79.         {
  80.             // Get exceptions here sometimes, so let's just ignore them
  81.             // since the window is going away anyhow
  82.             ERROR(ex);
  83.         }
  84.     },
  85.  
  86.     updateOption: function(name, value)
  87.     {
  88.         // XXXjjb Honza, if Console.isEnabled(context) false, then this can't be called,
  89.         // but somehow seems not correct
  90.         if (name == "showXMLHttpRequests")
  91.         {
  92.             var tach = value ? this.attachObserver : this.detachObserver;
  93.             for (var i = 0; i < TabWatcher.contexts.length; ++i)
  94.             {
  95.                 var context = TabWatcher.contexts[i];
  96.                 iterateWindows(context.window, function(win)
  97.                 {
  98.                     tach.apply(this, [context, win]);
  99.                 });
  100.             }
  101.         }
  102.     },
  103.  
  104.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  105.     // Attaching Spy to XHR requests.
  106.  
  107.     /**
  108.      * Returns false if Spy should not be attached to XHRs executed by the specified window.
  109.      */
  110.     skipSpy: function(win)
  111.     {
  112.         if (!win)
  113.             return true;
  114.  
  115.         // Don't attach spy to chrome.
  116.         var uri = safeGetWindowLocation(win);
  117.         if (uri && (uri.indexOf("about:") == 0 || uri.indexOf("chrome:") == 0))
  118.             return true;
  119.     },
  120.  
  121.     attachObserver: function(context, win)
  122.     {
  123.         if (Firebug.Spy.skipSpy(win))
  124.             return;
  125.  
  126.         for (var i=0; i<contexts.length; ++i)
  127.         {
  128.             if ((contexts[i].context == context) && (contexts[i].win == win))
  129.                 return;
  130.         }
  131.  
  132.         // Register HTTP observers only once.
  133.         if (contexts.length == 0)
  134.         {
  135.             httpObserver.addObserver(SpyHttpObserver, "firebug-http-event", false);
  136.             SpyHttpActivityObserver.registerObserver();
  137.         }
  138.  
  139.         contexts.push({context: context, win: win});
  140.  
  141.     },
  142.  
  143.     detachObserver: function(context, win)
  144.     {
  145.         for (var i=0; i<contexts.length; ++i)
  146.         {
  147.             if (contexts[i].context == context)
  148.             {
  149.                 if (win && (contexts[i].win != win))
  150.                     continue;
  151.  
  152.                 contexts.splice(i, 1);
  153.  
  154.                 // If no context is using spy, remvove the (only one) HTTP observer.
  155.                 if (contexts.length == 0)
  156.                 {
  157.                     httpObserver.removeObserver(SpyHttpObserver, "firebug-http-event");
  158.                     SpyHttpActivityObserver.unregisterObserver();
  159.                 }
  160.  
  161.                 return;
  162.             }
  163.         }
  164.     },
  165.  
  166.     /**
  167.      * Return XHR object that is associated with specified request <i>nsIHttpChannel</i>.
  168.      * Returns null if the request doesn't represent XHR.
  169.      */
  170.     getXHR: function(request)
  171.     {
  172.         // Does also query-interface for nsIHttpChannel.
  173.         if (!(request instanceof Ci.nsIHttpChannel))
  174.             return null;
  175.  
  176.         try
  177.         {
  178.             var callbacks = request.notificationCallbacks;
  179.             return (callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null);
  180.         }
  181.         catch (exc)
  182.         {
  183.             if (exc.name == "NS_NOINTERFACE")
  184.             {
  185.             }
  186.         }
  187.  
  188.        return null;
  189.     },
  190. });
  191.  
  192. // ************************************************************************************************
  193.  
  194. /**
  195.  * @class This observer uses {@link HttpRequestObserver} to monitor start and end of all XHRs.
  196.  * using <code>http-on-modify-request</code>, <code>http-on-examine-response</code> and
  197.  * <code>http-on-examine-cached-response</code> events. For every monitored XHR a new 
  198.  * instance of {@link Firebug.Spy.XMLHttpRequestSpy} object is created. This instance is removed
  199.  * when the XHR is finished.
  200.  */
  201. var SpyHttpObserver =
  202. /** @lends SpyHttpObserver */
  203. {
  204.     observe: function(request, topic, data)
  205.     {
  206.         try
  207.         {
  208.             if (topic != "http-on-modify-request" &&
  209.                 topic != "http-on-examine-response" &&
  210.                 topic != "http-on-examine-cached-response")
  211.             {
  212.                 return;
  213.             }
  214.  
  215.             this.observeRequest(request, topic);
  216.         }
  217.         catch (exc)
  218.         {
  219.         }
  220.     },
  221.  
  222.     observeRequest: function(request, topic)
  223.     {
  224.         var win = getWindowForRequest(request);
  225.         var xhr = Firebug.Spy.getXHR(request);
  226.  
  227.         // The request must be associated with window (i.e. tab) and it also must be 
  228.         // real XHR request.
  229.         if (!win || !xhr)
  230.             return;
  231.  
  232.         for (var i=0; i<contexts.length; ++i)
  233.         {
  234.             var context = contexts[i];
  235.             if (context.win == win)
  236.             {
  237.                 var spyContext = context.context;
  238.                 var requestName = request.URI.asciiSpec;
  239.                 var requestMethod = request.requestMethod;
  240.  
  241.                 if (topic == "http-on-modify-request")
  242.                     this.requestStarted(request, xhr, spyContext, requestMethod, requestName);
  243.                 else if (topic == "http-on-examine-response")
  244.                     this.requestStopped(request, xhr, spyContext, requestMethod, requestName);
  245.                 else if (topic == "http-on-examine-cached-response")
  246.                     this.requestStopped(request, xhr, spyContext, requestMethod, requestName);
  247.  
  248.                 return;
  249.             }
  250.         }
  251.     },
  252.  
  253.     requestStarted: function(request, xhr, context, method, url)
  254.     {
  255.         var spy = getSpyForXHR(request, xhr, context);
  256.         spy.method = method;
  257.         spy.href = url;
  258.  
  259.         if (method == "POST" || method == "PUT")
  260.             spy.postText = readPostTextFromRequest(request, context);
  261.  
  262.         spy.urlParams = parseURLParams(spy.href);
  263.  
  264.         // In case of redirects there is no stack and the source link is null.
  265.         spy.sourceLink = getStackSourceLink();
  266.  
  267.         if (!spy.requestHeaders)
  268.             spy.requestHeaders = getRequestHeaders(spy);
  269.  
  270.         // If it's enabled log the request into the console tab.
  271.         if (Firebug.showXMLHttpRequests && Firebug.Console.isAlwaysEnabled())
  272.         {
  273.             spy.logRow = Firebug.Console.log(spy, spy.context, "spy", null, true);
  274.             setClass(spy.logRow, "loading");
  275.         }
  276.  
  277.         // Notify registered listeners. The onStart event is fired once for entire XHR
  278.         // (even if there is more redirects within the process).
  279.         var name = request.URI.asciiSpec;
  280.         var origName = request.originalURI.asciiSpec;
  281.         if (name == origName)
  282.             dispatch(Firebug.Spy.fbListeners, "onStart", [context, spy]);
  283.  
  284.         // Remember the start time et the end, so it's most accurate.
  285.         spy.sendTime = new Date().getTime();
  286.     },
  287.  
  288.     requestStopped: function(request, xhr, context, method, url)
  289.     {
  290.         var spy = getSpyForXHR(request, xhr, context);
  291.         if (!spy)
  292.             return;
  293.  
  294.         spy.endTime = new Date().getTime();
  295.         spy.responseTime = spy.endTime - spy.sendTime;
  296.         spy.mimeType = Firebug.NetMonitor.Utils.getMimeType(request.contentType, request.name);
  297.  
  298.         if (!spy.responseHeaders)
  299.             spy.responseHeaders = getResponseHeaders(spy);
  300.  
  301.         if (!spy.statusText)
  302.         {
  303.             try
  304.             {
  305.                 spy.statusCode = request.responseStatus;
  306.                 spy.statusText = request.responseStatusText;
  307.             }
  308.             catch (exc)
  309.             {
  310.             }
  311.         }
  312.  
  313.         if (spy.logRow)
  314.         {
  315.             updateLogRow(spy);
  316.             updateHttpSpyInfo(spy);
  317.         }
  318.  
  319.         // Remove only the Spy object that has been created for an intermediate rediret
  320.         // request. These exist only to be also displayed in the console and they
  321.         // don't attach any listeners to the original XHR object (which is always created
  322.         // only once even in case of redirects).
  323.         // xxxHonza: These requests are not observer by the activityObserver now
  324.         // (if they should be observed we have to remove them in the activityObserver)
  325.         if (!spy.onLoad && spy.context.spies)
  326.             remove(spy.context.spies, spy);
  327.  
  328.     }
  329. };
  330.  
  331. // ************************************************************************************************
  332. // Activity Observer
  333.  
  334. /**
  335.  * @class This observer is used to properly monitor even mulipart XHRs. It's based on
  336.  * an activity-observer component that has been introduced in Firefox 3.6.
  337.  */
  338. var SpyHttpActivityObserver = extend(Firebug.NetMonitor.NetHttpActivityObserver,
  339. /** @lends SpyHttpActivityObserver */
  340. {
  341.     activeRequests: [],
  342.  
  343.     observeRequest: function(request, activityType, activitySubtype, timestamp,
  344.         extraSizeData, extraStringData)
  345.     {
  346.         if (activityType != Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION &&
  347.            (activityType == Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_SOCKET_TRANSPORT &&
  348.             activitySubtype != Ci.nsISocketTransport.STATUS_RECEIVING_FROM))
  349.             return;
  350.  
  351.         var win = getWindowForRequest(request);
  352.         if (!win)
  353.         {
  354.             var index = this.activeRequests.indexOf(request);
  355.             if (!(win = this.activeRequests[index+1]))
  356.                 return;
  357.         }
  358.  
  359.         for (var i=0; i<contexts.length; ++i)
  360.         {
  361.             var context = contexts[i];
  362.             if (context.win == win)
  363.             {
  364.                 var spyContext = context.context;
  365.                 var spy = getSpyForXHR(request, null, spyContext, true);
  366.                 if (spy)
  367.                     this.observeXHRActivity(win, spy, request, activitySubtype, timestamp);
  368.                 return;
  369.             }
  370.         }
  371.     },
  372.  
  373.     observeXHRActivity: function(win, spy, request, activitySubtype, timestamp)
  374.     {
  375.         // Activity observer has precise time info so, use it.
  376.         var time = new Date();
  377.         time.setTime(timestamp/1000);
  378.  
  379.         if (activitySubtype == Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_HEADER)
  380.         {
  381.             this.activeRequests.push(request);
  382.             this.activeRequests.push(win);
  383.  
  384.             spy.sendTime = time;
  385.             spy.transactionStarted = true;
  386.         }
  387.         else if (activitySubtype == Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE)
  388.         {
  389.             var index = this.activeRequests.indexOf(request);
  390.             this.activeRequests.splice(index, 2);
  391.  
  392.             spy.endTime = time;
  393.             spy.transactionClosed = true;
  394.  
  395.             // This should be the proper time to detach the Spy object, but only
  396.             // in the case when the XHR is already loaded. If the XHR is made as part of the
  397.             // page load, it may happen that the event (readyState == 4) comes later
  398.             // than actual TRANSACTION_CLOSE.
  399.             if (spy.loaded)
  400.                 spy.detach();
  401.         }
  402.         else if (activitySubtype == Ci.nsISocketTransport.STATUS_RECEIVING_FROM)
  403.         {
  404.             spy.endTime = time;
  405.         }
  406.     }
  407. });
  408.  
  409. // ************************************************************************************************
  410.  
  411. function getSpyForXHR(request, xhrRequest, context, noCreate)
  412. {
  413.     var spy = null;
  414.  
  415.     // Iterate all existing spy objects in this context and look for one that is
  416.     // already created for this request.
  417.     var length = context.spies.length;
  418.     for (var i=0; i<length; i++)
  419.     {
  420.         spy = context.spies[i];
  421.         if (spy.request == request)
  422.             return spy;
  423.     }
  424.  
  425.     if (noCreate)
  426.         return null;
  427.  
  428.     spy = new Firebug.Spy.XMLHttpRequestSpy(request, xhrRequest, context);
  429.     context.spies.push(spy);
  430.  
  431.     var name = request.URI.asciiSpec;
  432.     var origName = request.originalURI.asciiSpec;
  433.  
  434.     // Attach spy only to the original request. Notice that there can be more network requests
  435.     // made by the same XHR if redirects are involved.
  436.     if (name == origName)
  437.         spy.attach();
  438.  
  439.     return spy;
  440. }
  441.  
  442. // ************************************************************************************************
  443.  
  444. /**
  445.  * @class This class represents a Spy object that is attached to XHR. This object
  446.  * registers various listeners into the XHR in order to monitor various events fired
  447.  * during the request process (onLoad, onAbort, etc.)
  448.  */
  449. Firebug.Spy.XMLHttpRequestSpy = function(request, xhrRequest, context)
  450. {
  451.     this.request = request;
  452.     this.xhrRequest = xhrRequest;
  453.     this.context = context;
  454.     this.responseText = "";
  455.  
  456.     // For compatibility with the Net templates.
  457.     this.isXHR = true;
  458.  
  459.     // Support for activity-observer
  460.     this.transactionStarted = false;
  461.     this.transactionClosed = false;
  462. };
  463.  
  464. Firebug.Spy.XMLHttpRequestSpy.prototype =
  465. /** @lends Firebug.Spy.XMLHttpRequestSpy */
  466. {
  467.     attach: function()
  468.     {
  469.         var spy = this;
  470.         this.onReadyStateChange = function(event) { onHTTPSpyReadyStateChange(spy, event); };
  471.         this.onLoad = function() { onHTTPSpyLoad(spy); };
  472.         this.onError = function() { onHTTPSpyError(spy); };
  473.         this.onAbort = function() { onHTTPSpyAbort(spy); };
  474.  
  475.         // xxxHonza: #502959 is still failing on Fx 3.5
  476.         // Use activity distributor to identify 3.6 
  477.         if (SpyHttpActivityObserver.getActivityDistributor())
  478.         {
  479.             this.onreadystatechange = this.xhrRequest.onreadystatechange;
  480.             this.xhrRequest.onreadystatechange = this.onReadyStateChange;
  481.         }
  482.  
  483.         this.xhrRequest.addEventListener("load", this.onLoad, false);
  484.         this.xhrRequest.addEventListener("error", this.onError, false);
  485.         this.xhrRequest.addEventListener("abort", this.onAbort, false);
  486.  
  487.         // xxxHonza: should be removed from FB 3.6
  488.         if (!SpyHttpActivityObserver.getActivityDistributor())
  489.             this.context.sourceCache.addListener(this);
  490.     },
  491.  
  492.     detach: function()
  493.     {
  494.         // Bubble out if already detached.
  495.         if (!this.onLoad)
  496.             return;
  497.  
  498.         // If the activity distributor is available, let's detach it when the XHR
  499.         // transaction is closed. Since, in case of multipart XHRs the onLoad method
  500.         // (readyState == 4) can be called mutliple times.
  501.         // Keep in mind:
  502.         // 1) It can happen that that the TRANSACTION_CLOSE event comes before
  503.         // the onLoad (if the XHR is made as part of the page load) so, detach if
  504.         // it's already closed.
  505.         // 2) In case of immediate cache responses, the transaction doesn't have to
  506.         // be started at all (or the activity observer is no available in Firefox 3.5).
  507.         // So, also detach in this case.
  508.         if (this.transactionStarted && !this.transactionClosed)
  509.             return;
  510.  
  511.         remove(this.context.spies, this);
  512.  
  513.         if (this.onreadystatechange)
  514.             this.xhrRequest.onreadystatechange = this.onreadystatechange;
  515.  
  516.         try { this.xhrRequest.removeEventListener("load", this.onLoad, false); } catch (e) {}
  517.         try { this.xhrRequest.removeEventListener("error", this.onError, false); } catch (e) {}
  518.         try { this.xhrRequest.removeEventListener("abort", this.onAbort, false); } catch (e) {}
  519.  
  520.         this.onreadystatechange = null;
  521.         this.onLoad = null;
  522.         this.onError = null;
  523.         this.onAbort = null;
  524.  
  525.         // xxxHonza: shouuld be removed from FB 1.6
  526.         if (!SpyHttpActivityObserver.getActivityDistributor())
  527.             this.context.sourceCache.removeListener(this);
  528.     },
  529.  
  530.     getURL: function()
  531.     {
  532.         return this.xhrRequest.channel ? this.xhrRequest.channel.name : this.href;
  533.     },
  534.  
  535.     // Cache listener
  536.     onStopRequest: function(context, request, responseText)
  537.     {
  538.         if (!responseText)
  539.             return;
  540.  
  541.         if (request == this.request)
  542.             this.responseText = responseText;
  543.     },
  544. };
  545.  
  546. // ************************************************************************************************
  547.  
  548. function onHTTPSpyReadyStateChange(spy, event)
  549. {
  550.     var originalHandler = spy.onreadystatechange;
  551.  
  552.     // Force response text to be updated in the UI (in case the console entry
  553.     // has been already expanded and the response tab selected).
  554.     if (spy.logRow && spy.xhrRequest.readyState >= 3)
  555.     {
  556.         var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody");
  557.         if (netInfoBox)
  558.         {
  559.             netInfoBox.htmlPresented = false;
  560.             netInfoBox.responsePresented = false;
  561.         }
  562.     }
  563.  
  564.     // If the request is loading update the end time.
  565.     if (spy.xhrRequest.readyState == 3)
  566.     {
  567.         spy.responseTime = spy.endTime - spy.sendTime;
  568.         updateTime(spy);
  569.     }
  570.  
  571.     // Request loaded. Get all the info from the request now, just in case the 
  572.     // XHR would be aborted in the original onReadyStateChange handler.
  573.     if (spy.xhrRequest.readyState == 4)
  574.     {
  575.         // Cumulate response so, multipart response content is properly displayed.
  576.         if (SpyHttpActivityObserver.getActivityDistributor())
  577.             spy.responseText += spy.xhrRequest.responseText;
  578.         else
  579.         {
  580.             // xxxHonza: remove from FB 1.6
  581.             if (!spy.responseText)
  582.                 spy.responseText = spy.xhrRequest.responseText;
  583.         }
  584.  
  585.         // The XHR is loaded now (used also by the activity observer).
  586.         spy.loaded = true;
  587.  
  588.         // Update UI.
  589.         updateHttpSpyInfo(spy);
  590.  
  591.         // Notify Net pane about a request beeing loaded.
  592.         // xxxHonza: I don't think this is necessary.
  593.         var netProgress = spy.context.netProgress;
  594.         if (netProgress)
  595.             netProgress.post(netProgress.stopFile, [spy.request, spy.endTime, spy.postText, spy.responseText]);
  596.  
  597.         // Notify registered listeners about finish of the XHR.
  598.         dispatch(Firebug.Spy.fbListeners, "onLoad", [spy.context, spy]);
  599.     }
  600.  
  601.     // Pass the event to the original page handler.
  602.     callPageHandler(spy, event, originalHandler);
  603. }
  604.  
  605. function onHTTPSpyLoad(spy)
  606. {
  607.     spy.detach();
  608.  
  609.     // xxxHonza: Still needed for Fx 3.5 (#502959)
  610.     if (!SpyHttpActivityObserver.getActivityDistributor())
  611.         onHTTPSpyReadyStateChange(spy, null);
  612. }
  613.  
  614. function onHTTPSpyError(spy)
  615. {
  616.     spy.detach();
  617.     spy.loaded = true;
  618.  
  619.     if (spy.logRow)
  620.     {
  621.         removeClass(spy.logRow, "loading");
  622.         setClass(spy.logRow, "error");
  623.     }
  624. }
  625.  
  626. function onHTTPSpyAbort(spy)
  627. {
  628.     spy.detach();
  629.     spy.loaded = true;
  630.  
  631.     if (spy.logRow)
  632.     {
  633.         removeClass(spy.logRow, "loading");
  634.         setClass(spy.logRow, "error");
  635.     }
  636.  
  637.     spy.statusText = "Aborted";
  638.     updateLogRow(spy);
  639.  
  640.     // Notify Net pane about a request beeing aborted.
  641.     // xxxHonza: the net panel shoud find out this itself.
  642.     var netProgress = spy.context.netProgress;
  643.     if (netProgress)
  644.         netProgress.post(netProgress.abortFile, [spy.request, spy.endTime, spy.postText, spy.responseText]);
  645. }
  646.  
  647. // ************************************************************************************************
  648.  
  649. function callPageHandler(spy, event, originalHandler)
  650. {
  651.     try
  652.     {
  653.         // Calling the page handler throwed an exception (see #502959)
  654.         // This should be fixed in Firefox 3.5
  655.         if (originalHandler)
  656.             originalHandler.handleEvent(event);
  657.     }
  658.     catch (exc)
  659.     {
  660.         var error = Firebug.Errors.reparseXPC(exc, spy.context);
  661.         if (error)
  662.         {
  663.             // TODO attach trace
  664.             throw new Error(error.message, error.href, error.lineNo);
  665.         }
  666.     }
  667. }
  668.  
  669. // ************************************************************************************************
  670.  
  671. /**
  672.  * @domplate Represents a template for XHRs logged in the Console panel. The body of the
  673.  * log (displayed when expanded) is rendered using {@link Firebug.NetMonitor.NetInfoBody}.
  674.  */
  675. Firebug.Spy.XHR = domplate(Firebug.Rep,
  676. /** @lends Firebug.Spy.XHR */
  677. {
  678.     tag:
  679.         DIV({"class": "spyHead", _repObject: "$object"},
  680.             TABLE({"class": "spyHeadTable focusRow outerFocusRow", cellpadding: 0, cellspacing: 0,
  681.                 "role": "listitem", "aria-expanded": "false"},
  682.                 TBODY({"role": "presentation"},
  683.                     TR({"class": "spyRow"},
  684.                         TD({"class": "spyTitleCol spyCol", onclick: "$onToggleBody"},
  685.                             DIV({"class": "spyTitle"},
  686.                                 "$object|getCaption"
  687.                             ),
  688.                             DIV({"class": "spyFullTitle spyTitle"},
  689.                                 "$object|getFullUri"
  690.                             )
  691.                         ),
  692.                         TD({"class": "spyCol"},
  693.                             DIV({"class": "spyStatus"}, "$object|getStatus")
  694.                         ),
  695.                         TD({"class": "spyCol"},
  696.                             IMG({"class": "spyIcon", src: "blank.gif"})
  697.                         ),
  698.                         TD({"class": "spyCol"},
  699.                             SPAN({"class": "spyTime"})
  700.                         ),
  701.                         TD({"class": "spyCol"},
  702.                             TAG(FirebugReps.SourceLink.tag, {object: "$object.sourceLink"})
  703.                         )
  704.                     )
  705.                 )
  706.             )
  707.         ),
  708.  
  709.     getCaption: function(spy)
  710.     {
  711.         return spy.method.toUpperCase() + " " + cropString(spy.getURL(), 100);
  712.     },
  713.  
  714.     getFullUri: function(spy)
  715.     {
  716.         return spy.method.toUpperCase() + " " + spy.getURL();
  717.     },
  718.  
  719.     getStatus: function(spy)
  720.     {
  721.         var text = "";
  722.         if (spy.statusCode)
  723.             text += spy.statusCode + " ";
  724.  
  725.         if (spy.statusText)
  726.             return text += spy.statusText;
  727.  
  728.         return text;
  729.     },
  730.  
  731.     onToggleBody: function(event)
  732.     {
  733.         var target = event.currentTarget;
  734.         var logRow = getAncestorByClass(target, "logRow-spy");
  735.  
  736.         if (isLeftClick(event))
  737.         {
  738.             toggleClass(logRow, "opened");
  739.  
  740.             var spy = getChildByClass(logRow, "spyHead").repObject;
  741.             var spyHeadTable = getAncestorByClass(target, "spyHeadTable");
  742.  
  743.             if (hasClass(logRow, "opened"))
  744.             {
  745.                 updateHttpSpyInfo(spy);
  746.                 if (spyHeadTable)
  747.                     spyHeadTable.setAttribute('aria-expanded', 'true');
  748.             }
  749.             else
  750.             {
  751.                 var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody");
  752.                 dispatch(Firebug.NetMonitor.NetInfoBody.fbListeners, "destroyTabBody", [netInfoBox, spy]);
  753.                 if (spyHeadTable)
  754.                     spyHeadTable.setAttribute('aria-expanded', 'false');
  755.             }
  756.         }
  757.     },
  758.  
  759.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  760.  
  761.     copyURL: function(spy)
  762.     {
  763.         copyToClipboard(spy.getURL());
  764.     },
  765.  
  766.     copyParams: function(spy)
  767.     {
  768.         var text = spy.postText;
  769.         if (!text)
  770.             return;
  771.  
  772.         var url = reEncodeURL(spy, text, true);
  773.         copyToClipboard(url);
  774.     },
  775.  
  776.     copyResponse: function(spy)
  777.     {
  778.         copyToClipboard(spy.responseText);
  779.     },
  780.  
  781.     openInTab: function(spy)
  782.     {
  783.         openNewTab(spy.getURL(), spy.postText);
  784.     },
  785.  
  786.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  787.  
  788.     supportsObject: function(object)
  789.     {
  790.         return object instanceof Firebug.Spy.XMLHttpRequestSpy;
  791.     },
  792.  
  793.     browseObject: function(spy, context)
  794.     {
  795.         var url = spy.getURL();
  796.         openNewTab(url);
  797.         return true;
  798.     },
  799.  
  800.     getRealObject: function(spy, context)
  801.     {
  802.         return spy.xhrRequest;
  803.     },
  804.  
  805.     getContextMenuItems: function(spy)
  806.     {
  807.         var items = [
  808.             {label: "CopyLocation", command: bindFixed(this.copyURL, this, spy) }
  809.         ];
  810.  
  811.         if (spy.postText)
  812.         {
  813.             items.push(
  814.                 {label: "CopyLocationParameters", command: bindFixed(this.copyParams, this, spy) }
  815.             );
  816.         }
  817.  
  818.         items.push(
  819.             {label: "CopyResponse", command: bindFixed(this.copyResponse, this, spy) },
  820.             "-",
  821.             {label: "OpenInTab", command: bindFixed(this.openInTab, this, spy) }
  822.         );
  823.  
  824.         return items;
  825.     }
  826. });
  827.  
  828. // ************************************************************************************************
  829.  
  830. Firebug.XHRSpyListener =
  831. {
  832.     onStart: function(context, spy)
  833.     {
  834.     },
  835.  
  836.     onLoad: function(context, spy)
  837.     {
  838.     }
  839. };
  840.  
  841. // ************************************************************************************************
  842.  
  843. function updateTime(spy)
  844. {
  845.     var timeBox = spy.logRow.getElementsByClassName("spyTime").item(0);
  846.     if (spy.responseTime)
  847.         timeBox.textContent = " " + formatTime(spy.responseTime);
  848. }
  849.  
  850. function updateLogRow(spy)
  851. {
  852.     updateTime(spy);
  853.  
  854.     var statusBox = spy.logRow.getElementsByClassName("spyStatus").item(0);
  855.     statusBox.textContent = Firebug.Spy.XHR.getStatus(spy);
  856.  
  857.     removeClass(spy.logRow, "loading");
  858.     setClass(spy.logRow, "loaded");
  859.  
  860.     try
  861.     {
  862.         var errorRange = Math.floor(spy.xhrRequest.status/100);
  863.         if (errorRange == 4 || errorRange == 5)
  864.             setClass(spy.logRow, "error");
  865.     }
  866.     catch (exc)
  867.     {
  868.     }
  869. }
  870.  
  871. function updateHttpSpyInfo(spy)
  872. {
  873.     if (!spy.logRow || !hasClass(spy.logRow, "opened"))
  874.         return;
  875.  
  876.     if (!spy.params)
  877.         spy.params = parseURLParams(spy.href+"");
  878.  
  879.     if (!spy.requestHeaders)
  880.         spy.requestHeaders = getRequestHeaders(spy);
  881.  
  882.     if (!spy.responseHeaders && spy.loaded)
  883.         spy.responseHeaders = getResponseHeaders(spy);
  884.  
  885.     var template = Firebug.NetMonitor.NetInfoBody;
  886.     var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody");
  887.     if (!netInfoBox)
  888.     {
  889.         var head = getChildByClass(spy.logRow, "spyHead");
  890.         netInfoBox = template.tag.append({"file": spy}, head);
  891.         dispatch(template.fbListeners, "initTabBody", [netInfoBox, spy]);
  892.         template.selectTabByName(netInfoBox, "Response");
  893.     }
  894.     else
  895.     {
  896.         template.updateInfo(netInfoBox, spy, spy.context);
  897.     }
  898. }
  899.  
  900. // ************************************************************************************************
  901.  
  902. function getRequestHeaders(spy)
  903. {
  904.     var headers = [];
  905.  
  906.     var channel = spy.xhrRequest.channel;
  907.     if (channel instanceof Ci.nsIHttpChannel)
  908.     {
  909.         channel.visitRequestHeaders({
  910.             visitHeader: function(name, value)
  911.             {
  912.                 headers.push({name: name, value: value});
  913.             }
  914.         });
  915.     }
  916.  
  917.     return headers;
  918. }
  919.  
  920. function getResponseHeaders(spy)
  921. {
  922.     var headers = [];
  923.  
  924.     try
  925.     {
  926.         var channel = spy.xhrRequest.channel;
  927.         if (channel instanceof Ci.nsIHttpChannel)
  928.         {
  929.             channel.visitResponseHeaders({
  930.                 visitHeader: function(name, value)
  931.                 {
  932.                     headers.push({name: name, value: value});
  933.                 }
  934.             });
  935.         }
  936.     }
  937.     catch (exc)
  938.     {
  939.     }
  940.  
  941.     return headers;
  942. }
  943.  
  944. // ************************************************************************************************
  945. // Tracing Listener
  946.  
  947. Firebug.Spy.TraceListener =
  948. {
  949.     onDump: function(message)
  950.     {
  951.         var prefix = "spy.";
  952.         var index = message.text.indexOf(prefix);
  953.         if (index == 0)
  954.         {
  955.             message.text = message.text.substr(prefix.length);
  956.             message.text = trim(message.text);
  957.             message.type = "DBG_SPY";
  958.         }
  959.     }
  960. };
  961.  
  962. // ************************************************************************************************
  963. // Registration
  964.  
  965. Firebug.registerModule(Firebug.Spy);
  966. Firebug.registerRep(Firebug.Spy.XHR);
  967.  
  968. // ************************************************************************************************
  969. }});
  970.